package lab2;

import java.io.*;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class GBN {
    private static InetAddress fromHost; //获取发送给本机的ip地址
    private static int fromPort;//获取发送给本机的端口号
    public static Map<Integer, DatagramPacket>  packets = new ConcurrentHashMap<>(); //线程安全的HashMap，用于暂未收到ack的数据报
    public static Map<Integer, Integer> ackMap = new ConcurrentHashMap<>(); //线程安全的HashMap

    /**
     * 从文件中读取数据
     * @param file 文件
     * @return 读取出的字节数组
     * @throws IOException IO异常
     */
    public static byte[] getByteArray(File file) throws IOException {
        FileInputStream fileInputStream = new FileInputStream(file);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[Configuration.maxDataLength]; //分配块大小
        int length;//读出的实际块大小
        while ((length = fileInputStream.read(buffer)) != -1) {
            byteArrayOutputStream.write(buffer, 0, length);
        }
        return byteArrayOutputStream.toByteArray();
    }

    /**
     * 发送数据给目的主机中的目的端口号
     * @param socket 发送方套接字
     * @param sendBytes 需要发送的字节数组
     * @param targetHost 目的ip
     * @param targetPort 目的端口号
     * @throws IOException IO异常
     */
    public static void send(DatagramSocket socket, byte[] sendBytes, InetAddress targetHost, int targetPort) throws IOException {
        int dataGramNumber = sendBytes.length / Configuration.maxDataLength; //需要发送的数据报的个数
        int lastDataGramLength = sendBytes.length % Configuration.maxDataLength; //最后一个数据报的大小
        int base = 0; //发送窗口的基数
        int oldBase = 0;
        int next = 0; //下一个需要发送的数据报的序号
        byte[] toSendBytes; //用于构造每个需要发送的数据报
        ReceiveACK receiveACK = new ReceiveACK(socket, ackMap);//构造用于接收ACK的线程
        receiveACK.start(); //开始接收ack
        int ackNumber = 0;
        while (next <= dataGramNumber || ackMap.size() != 0) {
            double a = 0;
            for (int i = 1; i<Configuration.timeOut*100000; i++){a+=i;}//延迟时间

            oldBase = base;
            int max_received_seq = -1;
            int min_received_seq = Integer.MAX_VALUE;
            int max_received_seq_minus_base = -1;
            for (Integer i : GBN.ackMap.keySet()){
                if (GBN.ackMap.get(i)!=-1){
                    if (i>max_received_seq){
                        max_received_seq = i;
                    }
                    if (i<min_received_seq){
                        min_received_seq = i;
                    }
                    if (i>max_received_seq_minus_base&&i<base){
                        max_received_seq_minus_base = i;
                    }
                }
            }
            if (min_received_seq!=max_received_seq){
                if (min_received_seq<base){
                    for(Integer i : GBN.ackMap.keySet()){
                        if ((i<=max_received_seq&&base<=i)||(i<=max_received_seq_minus_base)){
                            GBN.ackMap.remove(i);
                        }
                    }
                    base = (max_received_seq_minus_base+1)%Configuration.maxACKNumber;
                }
                else if(min_received_seq-base<Configuration.sendWindowSize) {
                    for(Integer i : GBN.ackMap.keySet()){
                        if (i<=max_received_seq){
                            GBN.ackMap.remove(i);
                        }
                    }
                    base = (max_received_seq+1)%Configuration.maxACKNumber;
                }
            }else {
                GBN.ackMap.remove(max_received_seq);
                if (max_received_seq!=base-1) {
                    base = max_received_seq+1;
                }
            }
            if (base!=oldBase){
                System.out.println("发送窗口滑动到了：(" + base + "," +  (base+Configuration.sendWindowSize)+")");
                System.out.println("重新开始计时！");
            }else {
                System.out.println("窗口不移动！");
            }
            ackNumber = base;
            if (next > dataGramNumber && ackMap.size() == 0){
                return;
            }
            if (next!=0){
                int minus = oldBase<=base?(base-oldBase)%Configuration.maxACKNumber:Configuration.maxACKNumber-(oldBase-base);
                next = next-(Configuration.sendWindowSize-minus);
            }
            int canSend = Configuration.sendWindowSize;//计算还可以发送多少数据报
            for (int i = 0; i < canSend; i++) {
                if (next > dataGramNumber){
                    next++;
                    continue;
                }
                int sequence = (ackNumber + i) % Configuration.maxACKNumber; //数据报的序列号
                int sendLength = next < dataGramNumber ? Configuration.maxDataLength : lastDataGramLength;
                int end = next < dataGramNumber ? 1 : 0; //标记所有报文是否全部到达
                //构造报文；报文格式：0-2字节：报文中数据的长度；3字节：报文序号；4-1028字节：报文携带的数据
                toSendBytes = new byte[sendLength + 4]; //预分配空间
                toSendBytes[0] = (byte) ((sendLength >> 8) & 0xff); //数据总长最多1024，即至少需要10位来进行编码，构造报文时按16位（2Byte）进行构造
                toSendBytes[1] = (byte) (sendLength & 0xff); //报文第一二字节总和表示数据长度
                toSendBytes[2] = (byte) sequence; //报文序号
                System.arraycopy(sendBytes, next * Configuration.maxDataLength, toSendBytes, 3, sendLength);///拷贝需要发送的数据到数据报
                toSendBytes[sendLength + 3] = (byte) end; //最后一个字节标记所有报文是否全部到达
                DatagramPacket datagramPacket = new DatagramPacket(toSendBytes, sendLength + 4, targetHost, targetPort);
                packets.put(sequence, datagramPacket);

                if (Math.random() > Configuration.sendLoss) { //成功发送，否则不发送该数据报（发送端丢包）
                    System.out.println("发送了： " + sequence);
                    socket.send(datagramPacket);
                }else {
                    System.out.println("丢包： " + sequence);
                }
                ackMap.put(sequence, -1);//标记该序号未收到ack
                next++;
            }
        }
    }

    /**
     * 接收方接收数据
     * @param socket 接收方的套接字
     * @return 接收到的数据
     * @throws IOException IO异常
     */
    public static byte[] receive(DatagramSocket socket) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] receiveBytes = new byte[Configuration.maxDataLength+4];//报文大小
        int length;
        int expect = 0;
        while (true) {
            DatagramPacket packet = new DatagramPacket(receiveBytes, receiveBytes.length);
            try {
                socket.receive(packet); //接收数据报
            } catch (IOException e) {
                break;
            }
            //解析报文；报文格式：0-2字节：报文中数据的长度；3字节：报文序号；4-1028字节：报文携带的数据
            length = receiveBytes[0] & 0xff;
            length = length << 8 | receiveBytes[1] & 0xff; //还原报文中数据的长度
            int sequence = receiveBytes[2] & 0xff; //接收到的序号
            fromHost = packet.getAddress(); //获取主机号
            fromPort = packet.getPort();//获取端口号
            byte[] arrivalData = Arrays.copyOfRange(receiveBytes, 3, 3 + length);//拷贝到达的数据报
            if (sequence!=expect) {
                byte[] receiveAck = new byte[2];//构造ack报文
                receiveAck[0] = (byte) ((expect-1)%Configuration.maxACKNumber); //接收到的序号
                receiveAck[1] = receiveBytes[length + 3];//标记整个数据传输是否结束
                DatagramPacket ackPocket = new DatagramPacket(receiveAck, receiveAck.length, fromHost, fromPort);  //在老的发送窗口范围内，不接收数据报，但发送ack
                socket.send(ackPocket);
                System.out.println("收到了不期望收到的序号：" + sequence);
            }
            else {
                byteArrayOutputStream.write(arrivalData, 0, arrivalData.length);
                expect = (expect+1)%Configuration.maxACKNumber;
                byte[] receiveAck = new byte[2];//构造ack报文
                receiveAck[0] = (byte) ((expect-1)%Configuration.maxACKNumber); //接收到的序号
                receiveAck[1] = receiveBytes[length + 3];//标记整个数据传输是否结束
                DatagramPacket ackPocket = new DatagramPacket(receiveAck, receiveAck.length, fromHost, fromPort);  //发送ack
                System.out.println("收到了期望的：" + sequence);
                if (Math.random() > Configuration.receiveLoss) {
                    socket.send(ackPocket);
                }else {
                    System.out.println("没有发送ack：" + sequence);
                }
            }
        }
        return byteArrayOutputStream.toByteArray();
    }

    public static void send(DatagramSocket socket, byte[] sendBytes) throws IOException {
        //client端重载send方法，因客户端IP和端口号在服务器端首先发送给客户端时已获得
        send(socket, sendBytes, fromHost, fromPort);
    }
    public static void receiveAndWriteToFile(FileOutputStream fileOutputStream, DatagramSocket socket) throws IOException {
        byte[] receiveBytes;
        while (true) { //使其一直处于接收状态
            receiveBytes = receive(socket);
            if (receiveBytes.length != 0) {
                System.out.println("----------接收成功----------");
                System.out.println("共收到<"+receiveBytes.length+">字节");
                fileOutputStream.write(receiveBytes);
                break;
            }
        }
    }
}
